// Copyright 2022 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Command luapp is a very simple preprocessor for .lua files which inlines
// "loadfile" statements.
//
// This is intended to allow normal-looking collections of .lua files to be used
// effectively as Redis scripts, which only allow, essentially, a single module
// (there are undocumented hacks where you can call other pre-loaded scripts by
// their SHA1, but this is fragile at best, and still requires an apparatus to
// load all necessary scripts in order to compute their hashes).
//
// This is designed to be run with a go:generate statement via `go run` on
// a single .lua file, which will then output an expanded equivalent with the
// .gen.lua extension.
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"
)

func check(err error) {
	if err != nil {
		panic(err)
	}
}

func checkWrite(_ int, err error) {
	if err != nil {
		panic(err)
	}
}

var comment = regexp.MustCompile(`^\s*--`)
var pattern = regexp.MustCompile(`(.*)loadfile\(['"]([^)]+)['"]\)(.*)`)

func process(filename string) {
	ifile, err := os.Open(filename)
	check(err)
	defer func() {
		check(ifile.Close())
	}()

	destPath := strings.Replace(filename, ".lua", ".gen.lua", 1)

	tfile, err := os.Create(destPath + ".tmp")
	check(err)

	checkWrite(tfile.WriteString("-- Code generated by luapp. DO NOT EDIT.\n\n"))

	scn := bufio.NewScanner(ifile)
	for scn.Scan() {
		line := scn.Bytes()

		if comment.Match(line) {
			continue
		}

		if m := pattern.FindSubmatch(line); len(m) > 0 {
			lhs, filename, rhs := m[1], m[2], m[3]
			checkWrite(tfile.Write(lhs))
			checkWrite(tfile.WriteString("(function(...)\n"))

			importName := string(filename)
			fmt.Println("  --import", importName)
			toImport, err := os.Open(importName)
			check(err)
			defer func() {
				check(toImport.Close())
			}()

			reader := bufio.NewReader(toImport)
			for {
				line, err := reader.ReadString('\n')
				if err == io.EOF {
					checkWrite(tfile.WriteString(line))
					break
				}
				check(err)

				if comment.MatchString(line) {
					continue
				}
				checkWrite(tfile.WriteString(line))
			}

			checkWrite(tfile.WriteString("end)"))
			checkWrite(tfile.Write(rhs))
			checkWrite(tfile.WriteString("\n"))
		} else {
			checkWrite(tfile.Write(line))
			checkWrite(tfile.WriteString("\n"))
		}
	}

	check(tfile.Close())

	check(os.Rename(tfile.Name(), destPath))
}

func main() {
	if len(os.Args) != 2 {
		fmt.Fprintln(os.Stderr, "expected exactly one .lua file as an argument")
		os.Exit(1)
	}
	file := os.Args[1]
	fmt.Println("processing", file)
	process(file)
}
